#pragma once

#include <string>
#include <memory>
#include "zen_metal_prepared.sh.h"
#include "zen_metal_type.h"
#include "zen_matrix.h"


namespace Zen { namespace Metal {
	class UBufferBase_ {
		Metal::Buffer m_ubuf;
	public:
		virtual Metal::Buffer & buffer() {
			return m_ubuf;
		}
	};
	
	template<uint32_t _MatOff>
	class UBufferPartMatrix_ : public virtual UBufferBase_ {
		int m_mat_info = -1;
	public:
		void updateMatrix(Mat4 const & m)
		{
			buffer().update(_MatOff, sizeof(m), &m);
		}
		void updateMatrix(Mat4 const & m, int info, bool dirty)
		{
			if(!dirty && m_mat_info == info) return;
			
			buffer().update(_MatOff, sizeof(m), &m);
			m_mat_info = info;
		}
	};
	
	template<uint32_t _ColorOff>
	class UBufferPartColor_ : public virtual UBufferBase_ {
	protected:
		int m_alpha_info = 0;
	public:
		
		void updateColor(Color4f const & c, float alpha, bool dirty)
		{
			if(!dirty && alpha == m_alpha_info) return;
			
			Color4f color = c;
			color.alpha *= alpha;
			buffer().update(_ColorOff, sizeof(color), &color);
			m_alpha_info = alpha;
		}
		void updateColor(Color4f const & color)
		{
			buffer().update(_ColorOff, sizeof(color), &color);
		}
	};
	
	template<uint32_t _RatioOff>
	class UBufferPartSizeRatio_ : public virtual UBufferBase_ {
	protected:
		float m_size_ratio_scale = 0;
	public:
		
		void updateSizeRatio(Vec2 const & size_ratio, float scale, bool dirty)
		{
			if(!dirty && m_size_ratio_scale == scale) return;
			
			buffer().update(_RatioOff, sizeof(Vec2), &size_ratio);
			m_size_ratio_scale = scale;
		}
		void updateSizeRatio(Vec2 const & size_ratio)
		{
			buffer().update(_RatioOff, sizeof(Vec2), &size_ratio);
		}
	};

	template<class _tUBufIN>
	class UBuffer {
	};
	template<> class UBuffer<MTUBufIN_MC>
	: public virtual UBufferBase_
	, public UBufferPartMatrix_<MTUBufIN_MC_M_OFF>
	, public UBufferPartColor_<MTUBufIN_MC_C_OFF>
	{
	public:
		typedef MTUBufIN_MC Type;
		UBuffer()
		{
			buffer().create(sizeof(MTUBufIN_MC));
		}
	};
	
	template<> class UBuffer<MTUBufIN_MCS>
	: public virtual UBufferBase_
	, public UBufferPartMatrix_<MTUBufIN_MCS_M_OFF>
	, public UBufferPartColor_<MTUBufIN_MCS_C_OFF>
	, public UBufferPartSizeRatio_<MTUBufIN_MCS_S_OFF>
	{
	public:
		typedef MTUBufIN_MCS Type;

		UBuffer()
		{
			buffer().create(sizeof(MTUBufIN_MCS));
		}
	};
	
	template<class _tVBufIN>
	class VBuffer
	{
	public:
		typedef _tVBufIN Type;
		enum { step = sizeof(Type) };

		
		Metal::Buffer & buffer() { return m_buffer; }
		
		size_t count()
		{
			return m_count;
		}
		void updateBuffer(_tVBufIN const * src, size_t count)
		{
			if(count != m_count)
			{
				m_buffer.create(sizeof(_tVBufIN)*count, src);
				m_count = count;
			}
			else
			{
				m_buffer.update(0, sizeof(_tVBufIN)*count, src);
			}
		}
	protected:
		Metal::Buffer m_buffer;
		size_t m_count = 0;
	};

	template<typename _tType>
	class IndexBuffer
	{
	public:
		typedef _tType Type;

		enum { step = sizeof(Type) };

		Metal::Buffer & buffer() { return m_buffer; }

		size_t count()
		{
			return m_count;
		}

		void updateIndexBuffer(_tType const * src, size_t count) {
			
			if(count != m_count)
			{
				m_buffer.create(step*count, src);
				m_count = count;
			}
			else
			{
				m_buffer.update(0, step*count, src);
			}
		}
	protected:
		Metal::Buffer m_buffer;

		size_t m_count = 0;
	};
}}

namespace Zen { namespace Metal {

	template<typename _tUBufIN, typename _tVBufIN>
	class ShaderKitPartUV : public ShaderKit {
	public:
		void render(UBuffer<_tUBufIN> & u, VBuffer<_tVBufIN> & v, eVMode m) {

				auto r = Metal::Render::Get();
				r->activeEffect(this->getID());
				r->setVertexBuffer(MT_VBUF_INDEX, v.buffer().getID(), 0);
				r->setVertexBuffer(MT_UBUF_INDEX, u.buffer().getID(), 0);
				r->drawPrimitives(m, 0, v.count());
		}
		template<class _IndexBuffer>
		void renderIndexed(UBuffer<_tUBufIN> & u, VBuffer<_tVBufIN> & v, eVMode m,
						   _IndexBuffer & i) {

			auto r = Metal::Render::Get();
			r->activeEffect(this->getID());
			r->setVertexBuffer(MT_VBUF_INDEX, v.buffer().getID(), 0);
			r->setVertexBuffer(MT_UBUF_INDEX, u.buffer().getID(), 0);
			r->drawIndexedPrimitives(m, i.count(), i.buffer().getID(), 0, _IndexBuffer::step);
		}

	};
	template<typename _tUBufIN, typename _tVBufIN>
	class ShaderKitPartUVT : public ShaderKit {
	public:
		void render(UBuffer<_tUBufIN> & u, VBuffer<_tVBufIN> & v, Metal::Texture & t, eVMode m) {

			auto r = Metal::Render::Get();
			r->activeEffect(this->getID());
			r->setVertexBuffer(MT_VBUF_INDEX, v.buffer().getID(), 0);
			r->setVertexBuffer(MT_UBUF_INDEX, u.buffer().getID(), 0);
			r->bindTexture(MT_TEXT_INDEX, t.getID());
			r->drawPrimitives(m, 0, v.count());
		}
		template<class _IndexBuffer>
		void renderIndexed(UBuffer<_tUBufIN> & u, VBuffer<_tVBufIN> & v, Metal::Texture & t, eVMode m,
						   _IndexBuffer & i) {

			auto r = Metal::Render::Get();
			r->activeEffect(this->getID());
			r->setVertexBuffer(MT_VBUF_INDEX, v.buffer().getID(), 0);
			r->setVertexBuffer(MT_UBUF_INDEX, u.buffer().getID(), 0);
			r->bindTexture(MT_TEXT_INDEX, t.getID());
			r->drawIndexedPrimitives(m, i.count(), i.buffer().getID(), 0, _IndexBuffer::step);
		}

	};

	/// pipex
	using ShaderKitColors = ShaderKitPartUV<MTUBufIN_MC, MTVBufIN_C>;
	class MTShaderKitColors : public ShaderKitColors
	{
	public:
		static
		std::shared_ptr<MTShaderKitColors>
		GetShared(bool grayfy, Blends const & blend);
	};

	using ShaderKitPoints =  ShaderKitPartUV<MTUBufIN_MCS, MTVBufIN_P>;
	class MTShaderKitPoints : public ShaderKitPoints
	{
	public:
		static
		std::shared_ptr<MTShaderKitPoints>
		GetShared(bool grayfy, Blends const & blends);
	};

	using ShaderKitTexture  = ShaderKitPartUVT<MTUBufIN_MC, MTVBufIN_T>;
	class MTShaderKitTexture : public ShaderKitTexture
	{
	public:
		static
		std::shared_ptr<ShaderKitTexture>

		GetShared(bool grayfy, ePixel fomrat, Blends const & blend);

	};

	using ShaderKitPT = ShaderKitPartUVT<MTUBufIN_MCS, MTVBufIN_P>;
	class MTShaderKitPT : public ShaderKitPT
	{
	public:
		static
		std::shared_ptr<MTShaderKitPT>
		GetShared(bool grayfy, ePixel fomrat, Blends const & blend);
	};

	/// pipex3D

	class ShaderKitColors3D : public ShaderKitPartUV<MTUBufIN_MC, MTVBufIN_C3D>
	{
	public:
		static
		std::shared_ptr<ShaderKitColors3D>
		GetShared(bool grayfy, Blends const & blend);
	};

	class ShaderKitP3D : public ShaderKitPartUV<MTUBufIN_MCS, MTVBufIN_P3D>
	{
	public:
		static
		std::shared_ptr<ShaderKitP3D>
		GetShared(bool grayfy, Blends const & blends);
	};

	class ShaderKitT3D : public ShaderKitPartUVT<MTUBufIN_MC, MTVBufIN_T3D>
	{
	public:
		static
		std::shared_ptr<ShaderKitT3D>
		GetShared(bool grayfy, ePixel fomrat, Blends const & blend);

	};

	class ShaderKitPT3D : public ShaderKitPartUVT<MTUBufIN_MCS, MTVBufIN_P3D>
	{
	public:
		static
		std::shared_ptr<ShaderKitPT3D>
		GetShared(bool grayfy, ePixel fomrat, Blends const & blend);

	};

}}
